﻿/*
   Copyright 2012 University of Southampton

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
*/

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Security.AccessControl;
using System.Security.Principal;
using System.Diagnostics;
using uk.ac.soton.ses.DAL;

namespace uk.ac.soton.ses
{
    public class HDCWatcherFolderCheck
    {
        private ExperimentMetadataContainerDB _dbMetadata;
        private ExperimentMetadataContainerXML _xmlMetadata;
        private ExperimentMetadataContainerFileSystem _fsMetadata;
        private HDCWatcherQueueItem_FolderCheckQuick _workItem;
        private Queue<HDCWatcherQueueItem> _nextWorkItems = new Queue<HDCWatcherQueueItem>();
        private bool _quickCheck = false;

        public ExperimentMetadataContainerDB DBMetadata { get { return this._dbMetadata; } set { this._dbMetadata = value; } }
        public ExperimentMetadataContainerXML XMLMetadata { get { return this._xmlMetadata; } set { this._xmlMetadata = value; } }
        public ExperimentMetadataContainerFileSystem FSMetadata { get { return this._fsMetadata; } set { this._fsMetadata = value; } }
        public HDCWatcherQueueItem_FolderCheckQuick WorkItem { get { return this._workItem; } set { this._workItem = value; } }
        private Queue<HDCWatcherQueueItem> NextWorkItems { get { return this._nextWorkItems; } }
        public string FullPath { get { return this._workItem.HDCPath.FullPath; } }

        public HDCWatcherFolderCheck(HDCWatcherQueueItem_FolderCheckQuick workItem, bool quickCheck)
        {
            this._workItem = workItem;
            this._quickCheck = quickCheck;
        }

        private void GetMetadata()
        {
            //Check the folder exists
            if (!Directory.Exists(this._workItem.HDCPath.BasePath))
            {
                string message = string.Format("Folder {0} does not exist, ignoring it. Triggering folder checking to clear up deleted folders.", this._workItem.HDCPath.BasePath);
                HDCFileSystemWatcher.Message(message, TraceLevel.Warning);
                HDCWatcherQueueProcessing.QueueMetadataCheck(this._workItem.FileSystemWatcher);
                this._workItem.QueueItemState = HDCWatcherQueueItemState.CompleteWithError;
                return;
            }

            //Load database and XML metadata
            try
            {
                //Try to find the XML file
                if (ExperimentMetadataContainerXML.MetadataExists(this._workItem.HDCPath.BasePath))
                {
                    this.LoadMetadataUsingXML();
                }
                //XML file doesn't exist yet
                else
                {
                    this.CreateNewMetadata(true, false);
                }
            }
            catch (ExperimentMetadataContainerException e)
            {
                string message = string.Format("Error with {0}: The metadata for this folder has become corrupted. Try checking the Experiment IDs in the XML file or deleting .experiment or the database record", this._workItem.HDCPath.BasePath);
                HDCFileSystemWatcher.Message(message, TraceLevel.Error);
                HDCFileSystemWatcher.Message(e.Message, TraceLevel.Error);
                this._workItem.QueueItemState = HDCWatcherQueueItemState.CompleteWithError;
                return;
            }
            catch (IOException)
            {
                string message = string.Format("Couldn't process {0} right now. Adding back onto items.", this._workItem.HDCPath.FullPath);
                HDCFileSystemWatcher.Message(message, TraceLevel.Info);

                this._workItem.QueueItemState = HDCWatcherQueueItemState.RetryRequired;
                return;
            }
            catch (Exception e)
            {
                string message = string.Format("Something went badly wrong with {0}'s metadata. Needs manual intervention.", this._workItem.HDCPath.BasePath);
                HDCFileSystemWatcher.Message(message, TraceLevel.Error);
                HDCFileSystemWatcher.Message(e.Message, TraceLevel.Error);
                this._workItem.QueueItemState = HDCWatcherQueueItemState.CompleteWithError;
                return;
            }

            this._workItem.QueueItemState = HDCWatcherQueueItemState.InProgress;
        }

        private void LoadMetadataUsingXML()
        {
            this.GetXMLMetadata();

            if (this._xmlMetadata != null)
            {
                //Load the DB data using the ID in the XML file
                if (ExperimentMetadataContainerDB.MetadataExists(this._xmlMetadata.Experiment.ID))
                {
                    this.LoadDBMetadataUsingXML();
                    this.LoadFSMetadata();

                    //Check whether the redirect page exists. If not, recreate it
                    if (!ExperimentMetadataContainerXML.DetailsPageRedirFileExists(this._workItem.HDCPath.BasePath))
                    {
                        string detailsPageRedirFilePath = ExperimentMetadataContainerXML.GetDetailsPageRedirFilePath(this._workItem.HDCPath.BasePath);
                        ExperimentMetadataContainerXML.SaveDetailsPageRedirFile(this._xmlMetadata.Experiment.ID, detailsPageRedirFilePath, true);
                        HDCFileSystemWatcher.Message("Recreated redirect file for experiment " + this._xmlMetadata.Experiment.ID, TraceLevel.Warning);
                    }
                }
                //XML file existed, but the database record has gone so we need to recreate it
                else
                {
                    this.CreateNewMetadata(true, true);

                    //Just to be sure, we'll reprocess this folder
                    HDCFileSystemWatcher.Message(string.Format("Database entry recreated. Adding {0} back onto list for reprocessing with new XML file", this._workItem.HDCPath.BasePath), TraceLevel.Warning);
                    this._workItem.QueueItemState = HDCWatcherQueueItemState.RetryRequired;
                }
            }
        }

        private void LoadFSMetadata()
        {
            if (this._quickCheck && this._dbMetadata != null)
            {
                this._fsMetadata = ExperimentMetadataContainer.LoadFromFileSystem(this._workItem.HDCPath, this._dbMetadata);
            }
            else
            {
                this._fsMetadata = ExperimentMetadataContainer.LoadFromFileSystem(this._workItem.HDCPath);
            }
        }

        private void GetXMLMetadata()
        {
            //Load the XML file if it exists
            this._xmlMetadata = ExperimentMetadataContainer.LoadFromXML(this._workItem.HDCPath.BasePath);
        }

        private void CreateDBMetadataFromFS()
        {
            //Create the new database record
            HDCFileSystemWatcher.Message(string.Format("Creating database record for experiment {0}", this._fsMetadata.Experiment.Name), TraceLevel.Warning);
            this._dbMetadata = this._fsMetadata.SaveToDB();
        }

        private void CreateXMLMetadataFromDB(bool backupExistingMetadata)
        {
            if (backupExistingMetadata)
            {
                this._xmlMetadata = this._dbMetadata.SaveToXML(this._xmlMetadata);
            }
            else
            {
                //Create the XML data from the DB data
                this._xmlMetadata = this._dbMetadata.SaveToXML(true);
            }
        }

        private void LoadDBMetadataUsingXML()
        {
            string folderPath = this.WorkItem.HDCPath.FullPath;

            HDCFileSystemWatcher.Message(string.Format("Found XML file, loading database record {0}", this._xmlMetadata.Experiment.ID), TraceLevel.Verbose);

            this._dbMetadata = ExperimentMetadataContainer.LoadFromDB(this._xmlMetadata.Experiment.ID);

            //Check db data is sane
            if (this._dbMetadata.Experiment.IsDeleted)
            {
                HDCFileSystemWatcher.Message(string.Format("Experiment {0} is marked as deleted in database. We need a new data record", folderPath), TraceLevel.Warning);

                this._xmlMetadata.BackupMetadata();
                this.CreateNewMetadata(false, false);

                HDCFileSystemWatcher.Message(string.Format("Experiment {0} now has ID {1}", folderPath, this._dbMetadata.Experiment.ID), TraceLevel.Warning);
            }
        }

        private void CreateNewMetadata(bool recheckExistingMetadata, bool backupExistingMetadata)
        {
            if (recheckExistingMetadata)
            {
                //We must ensure that existing metadata in the database pointing to this path is deleted.
                //The user may have deleted their metadata for a reason and we don't want to inadvertently put metadata onto the wrong folder
                HDCWatcherMetadataCheck.CheckExistingMetadata(_workItem.FileSystemWatcher);
            }

            if (this._fsMetadata == null)
            {
                this.LoadFSMetadata();
            }

            //Create a new database record
            this.CreateDBMetadataFromFS();

            //Create XML metadata file from DB record
            this.CreateXMLMetadataFromDB(backupExistingMetadata);//was false
        }

        public void FindAndProcessChanges()
        {
            this.GetMetadata();

            //We should only proceed if our work item is in the state that we expect
            if (this._workItem.QueueItemState != HDCWatcherQueueItemState.InProgress)
            {
                return;
            }

            //Get a list of files that have changed
            ExperimentMetadataContainerComparer comparer = new ExperimentMetadataContainerComparer(this._dbMetadata, this._fsMetadata);
            comparer.PrintDebugInfo();

            try
            {
                this.ProcessExperimentRename(ref comparer);
            }
            catch (Exception e)
            {
                string message = string.Format("Error with {0}: Sorry, something went wrong with looking in ProcessExperimentRename. Stopping processing this experiment.", this._workItem.HDCPath.BasePath);
                HDCFileSystemWatcher.Message(message, TraceLevel.Error);
                HDCFileSystemWatcher.Message(e.Message, TraceLevel.Error);
                this._workItem.QueueItemState = HDCWatcherQueueItemState.CompleteWithError;
                return;
            }

            //Process file renames first
            try
            {
                this.ProcessFileRenamesAndMoves(comparer);
            }
            catch (Exception e)
            {
                HDCFileSystemWatcher.Message(string.Format("Error with {0}: Sorry, something went wrong with looking in ProcessFileRenames. Stopping processing this experiment.", this._workItem.HDCPath.BasePath), TraceLevel.Error);
                HDCFileSystemWatcher.Message(e.Message, TraceLevel.Error);
                this._workItem.QueueItemState = HDCWatcherQueueItemState.CompleteWithError;
                return;
            }

            //Process file deletes next
            try
            {
                //ProcessFileRenames based on hash but only those that don't exist by filename
                this.ProcessFileDeletes(comparer);
            }
            catch (Exception e)
            {
                HDCFileSystemWatcher.Message(string.Format("Error with {0}: Sorry, something went wrong with looking in ProcessFileDeletes.", this._workItem.HDCPath.BasePath), TraceLevel.Error);
                HDCFileSystemWatcher.Message(e.Message,TraceLevel.Error);
                this._workItem.QueueItemState = HDCWatcherQueueItemState.CompleteWithError;
                return;
            }


            //Process file adds next (we do this last because we have to delete files first to ensure there are no duplicates)
            try
            {
                this.ProcessFileAdds(comparer);
            }
            catch (Exception e)
            {
                HDCFileSystemWatcher.Message(string.Format("Error with {0}: Sorry, something went wrong with looking in ProcessFileAdds. Stopping processing this experiment.", this._workItem.HDCPath.BasePath),TraceLevel.Error);
                HDCFileSystemWatcher.Message(e.Message, TraceLevel.Error);
                this._workItem.QueueItemState = HDCWatcherQueueItemState.CompleteWithError;
                return;
            }

            try
            {
                this.ProcessFileUpdates(comparer);
            }
            catch (Exception e)
            {
                HDCFileSystemWatcher.Message(string.Format("Error with {0}: Sorry, something went wrong with ProcessFileUpdates. Stopping processing this experiment.", this._workItem.HDCPath.BasePath), TraceLevel.Error);
                HDCFileSystemWatcher.Message(e.Message, TraceLevel.Error);
                this._workItem.QueueItemState = HDCWatcherQueueItemState.CompleteWithError;
                return;
            }

            try
            {

                this.ProcessFileTimeStampChanges(comparer);
            }
            catch (Exception e)
            {
                HDCFileSystemWatcher.Message(string.Format("Error with {0}: Sorry, something went wrong with ProcessFileTimeStampChanges. Stopping processing this experiment.", this._workItem.HDCPath.BasePath), TraceLevel.Error);
                HDCFileSystemWatcher.Message(e.Message, TraceLevel.Error);
                this._workItem.QueueItemState = HDCWatcherQueueItemState.CompleteWithError;
                return;
            }

            try
            {
                this.ProcessSecurity(comparer);
            }
            catch (Exception e)
            {
                HDCFileSystemWatcher.Message(string.Format("Error with {0}: Sorry, something went wrong with ProcessSecurity. Stopping processing this experiment.", this._workItem.HDCPath.BasePath), TraceLevel.Error);
                HDCFileSystemWatcher.Message(e.Message, TraceLevel.Error);
                this._workItem.QueueItemState = HDCWatcherQueueItemState.CompleteWithError;
                return;
            }

            try
            {
                comparer.SavedMetadata.SaveChanges();
            }
            catch (Exception e)
            {
                HDCFileSystemWatcher.Message(string.Format("Error with {0}: Couldn't save changes. Stopping processing this experiment", this._workItem.HDCPath.BasePath), TraceLevel.Error);
                HDCFileSystemWatcher.Message(e.Message, TraceLevel.Error);
                this._workItem.QueueItemState = HDCWatcherQueueItemState.CompleteWithError;
                return;
            }
        }

        /// <summary>
        /// Renames the experiment if necessary
        /// </summary>
        /// <param name="comparer">Pass the comparer to use</param>
        /// <returns>True if the experiment was renamed and false otherwise</returns>
        public void ProcessExperimentRename(ref ExperimentMetadataContainerComparer comparer)
        {
            if (comparer.WasRenamed || comparer.WasMoved)
            {
                bool wasDuplicated = false;
                string oldLocation = comparer.SavedMetadata.Experiment.DefaultBasePath;
                if (Directory.Exists(oldLocation)
                && ExperimentMetadataContainerXML.MetadataExists(oldLocation))
                {
                    //We think the folder was renamed but the old one still exists so we better check it wasn't copied
                    ExperimentMetadataContainerXML oldLocationData = ExperimentMetadataContainer.LoadFromXML(oldLocation);
                    wasDuplicated = (oldLocationData.Experiment.ID == comparer.SavedMetadata.Experiment.ID);
                }

                if (wasDuplicated)
                {
                    HDCFileSystemWatcher.Message(string.Format("This experiment {0} was copied, creating new data", comparer.SavedMetadata.Experiment.DefaultBasePath), TraceLevel.Warning);
                    this._xmlMetadata.BackupMetadata();
                    this._dbMetadata = this._fsMetadata.SaveToDB();
                    this._xmlMetadata = this._dbMetadata.SaveToXML(true);

                    //Generate a new comparer with the new data
                    comparer = new ExperimentMetadataContainerComparer(this._dbMetadata, this._fsMetadata);
                }
                else
                {
                    HDCFileSystemWatcher.Message(string.Format(
                            "  Folder '{0}' has moved: {1} => {2}. Updating metadata",
                            comparer.NewMetadata.Experiment.Name,
                            comparer.SavedMetadata.Experiment.DefaultBasePath,
                            comparer.NewMetadata.Experiment.DefaultBasePath
                        )
                        ,TraceLevel.Warning
                    );

                    comparer.SavedMetadata.RenameExperiment(comparer.NewMetadata.Experiment.Name, comparer.NewMetadata.Experiment.DefaultBasePath);
                    HDCFileSystemWatcher.Message("Getting new metadata", TraceLevel.Info);
                    this.GetMetadata();
                    comparer = new ExperimentMetadataContainerComparer(this._dbMetadata, this._fsMetadata);
                    comparer.PrintDebugInfo();
                }
            }
        }

        private void ProcessFileUpdates(ExperimentMetadataContainerComparer comparer)
        {
            foreach (ChangedExperimentDataFileBO updatedFile in comparer.ChangedFiles)
            {
                HDCFileSystemWatcher.Message(
                    string.Format("  Updated contents {0} [Flag:{1}]", updatedFile.SavedDataFile.ExperimentDataFile.GetFullPath(), updatedFile.ChangedFlags)
                    ,TraceLevel.Warning
                    );
                updatedFile.SavedDataFile.UpdateExperimentDataFile(updatedFile.NewDataFile.ExperimentDataFile);
            }
        }

        private void ProcessFileTimeStampChanges(ExperimentMetadataContainerComparer comparer)
        {
            foreach (ChangedExperimentDataFileBO updatedFile in comparer.ChangedTimeStampsFiles)
            {
                HDCFileSystemWatcher.Message(
                    string.Format("  Updated time stamps {0} [Flag:{1}]", updatedFile.SavedDataFile.ExperimentDataFile.GetFullPath(), updatedFile.ChangedFlags)
                    ,TraceLevel.Warning
                    );
                updatedFile.SavedDataFile.UpdateExperimentDataFile(updatedFile.NewDataFile.ExperimentDataFile);
            }
        }

        private void ProcessFileRenamesAndMoves(ExperimentMetadataContainerComparer comparer)
        {
            foreach (ChangedExperimentDataFileBO renamedFile in comparer.FilesRenamed)
            {
                HDCFileSystemWatcher.Message(
                    string.Format("File renamed {0} => {1} ", renamedFile.SavedDataFile.ExperimentDataFile.GetFullPath(), renamedFile.NewDataFile.ExperimentDataFile.GetFullPath())
                    , TraceLevel.Warning
                    );
                renamedFile.SavedDataFile.RenameExperimentDataFile(renamedFile.NewDataFile.ExperimentDataFile.RemainingPath, renamedFile.NewDataFile.ExperimentDataFile.BasePath);
            }
            foreach (ChangedExperimentDataFileBO movedFile in comparer.FilesMoved)
            {
                HDCFileSystemWatcher.Message(
                    string.Format("File moved {0} => {1} ", movedFile.SavedDataFile.ExperimentDataFile.GetFullPath(), movedFile.NewDataFile.ExperimentDataFile.GetFullPath())
                    , TraceLevel.Warning
                    );
                movedFile.SavedDataFile.RenameExperimentDataFile(movedFile.NewDataFile.ExperimentDataFile.RemainingPath, movedFile.NewDataFile.ExperimentDataFile.BasePath);
            }
            foreach (ChangedExperimentDataFileBO movedFile in comparer.FilesMovedAndRenamed)
            {
                HDCFileSystemWatcher.Message(
                    string.Format("File moved and renamed {0} => {1} ", movedFile.SavedDataFile.ExperimentDataFile.GetFullPath(), movedFile.NewDataFile.ExperimentDataFile.GetFullPath())
                    , TraceLevel.Warning
                    );
                movedFile.SavedDataFile.RenameExperimentDataFile(movedFile.NewDataFile.ExperimentDataFile.RemainingPath, movedFile.NewDataFile.ExperimentDataFile.BasePath);
            }
        }

        private void ProcessFileDeletes(ExperimentMetadataContainerComparer comparer)
        {
            List<ExperimentDataFileMetadataContainer> missingFiles = comparer.MissingFiles;

            int lengthOfArray = comparer.MissingFiles.Count;
            for (int i = 0; i < lengthOfArray; i++)
            {
                ExperimentDataFileMetadataContainer deletedFile = missingFiles[i];
                HDCFileSystemWatcher.Message(
                    string.Format("  Deleted {0}", deletedFile.ExperimentDataFile.GetFullPath())
                    , TraceLevel.Warning
                    );
                deletedFile.DeleteExperimentDataFile();
            }
        }

        private void ProcessFileAdds(ExperimentMetadataContainerComparer comparer)
        {
            foreach (ExperimentDataFileMetadataContainer createdFile in comparer.CreatedFiles)
            {
                HDCFileSystemWatcher.Message(string.Format("Adding file {0} to experiment {1} ({2})", createdFile.ExperimentDataFile.RemainingPath, comparer.SavedMetadata.Experiment.ID, comparer.SavedMetadata.Experiment.Name), TraceLevel.Warning);
                ExperimentDataFileMetadataContainer newFileData = comparer.SavedMetadata.AddExperimentDataFile(createdFile.ExperimentDataFile);

                if (newFileData is ExperimentDataFileMetadataContainerDB)
                {
                    //Execute whatever plugins exist for files of this extension
                    List<IHDCMetadataSyncServicePlugin> plugins = this.WorkItem.FileSystemWatcher.GetPluginsForExtension(Path.GetExtension(newFileData.ExperimentDataFile.FullPath));
                    foreach (IHDCMetadataSyncServicePlugin plugin in plugins)
                    {
                        HDCFileSystemWatcher.Message(string.Format("Executing plugin '{0}' for file '{1}' ({2})", plugin.Name, newFileData.ExperimentDataFile.RemainingPath, newFileData.ExperimentDataFile.ID), TraceLevel.Warning);
                        int count = plugin.ExecutePlugin(newFileData.ExperimentDataFile.ID);
                        HDCFileSystemWatcher.Message(string.Format("Finished executing plugin '{0}' for file '{1}' ({2}). {3} parameters added.", plugin.Name, newFileData.ExperimentDataFile.RemainingPath, newFileData.ExperimentDataFile.ID, count), TraceLevel.Warning);
                    }

                }
            }
        }

        private void ProcessSecurity(ExperimentMetadataContainerComparer comparer)
        {
            string[] fsreadusers = comparer.NewMetadata.Experiment.ReadUsers;
            string[] dbreadusers = comparer.SavedMetadata.Experiment.ReadUsers;
            string[] fswriteusers = comparer.NewMetadata.Experiment.WriteUsers;
            string[] dbwriteusers = comparer.SavedMetadata.Experiment.WriteUsers;

            this.ProcessFolderSecurity(comparer.NewMetadata.Experiment.DefaultBasePath, fswriteusers, dbwriteusers,true);
            this.ProcessFolderSecurity(comparer.NewMetadata.Experiment.DefaultBasePath, fsreadusers, dbreadusers, false);
        }

        private void ProcessFolderSecurity(string folderPath, string[] fsusers, string[] dbusers, bool grantWriteAccess)
        {
            DirectoryInfo directoryInfo = new DirectoryInfo(folderPath);
            DirectorySecurity directorySecurity = directoryInfo.GetAccessControl();
            AuthorizationRuleCollection fsrules = directorySecurity.GetAccessRules(true, false, typeof(SecurityIdentifier));
            InheritanceFlags ourInheritanceFlags = InheritanceFlags.ContainerInherit | InheritanceFlags.ObjectInherit;
            PropagationFlags ourPropagationFlags = PropagationFlags.None;
            FileSystemRights permissionsToGrant = grantWriteAccess ? FileSystemRights.Modify : FileSystemRights.ReadAndExecute;

            //Add users in database to file system
            foreach (string dbuser in dbusers)
            {
                if (!fsusers.Contains(dbuser))
                {
                    SecurityIdentifier newuserid = new SecurityIdentifier(dbuser);
                    NTAccount useracc = newuserid.Translate(typeof(NTAccount)) as NTAccount;

                    FileSystemAccessRule newrule = new FileSystemAccessRule(newuserid, permissionsToGrant, ourInheritanceFlags, ourPropagationFlags, AccessControlType.Allow);
                    directorySecurity.AddAccessRule(newrule);
                    directoryInfo.SetAccessControl(directorySecurity);
                    HDCFileSystemWatcher.Message(String.Format("Added {0} to file system (SID {1}:{2})", useracc.Value, newuserid.Value,grantWriteAccess?"Write":"Read"),TraceLevel.Warning);
                }
            }

            //Remove users from file system that have been deleted from database
            foreach (FileSystemAccessRule fsrule in fsrules.OfType<FileSystemAccessRule>())
            {
                NTAccount useracc = fsrule.IdentityReference.Translate(typeof(NTAccount)) as NTAccount;
                SecurityIdentifier userid = fsrule.IdentityReference.Translate(typeof(SecurityIdentifier)) as SecurityIdentifier;
                if (fsrule.IsInherited || (fsrule.InheritanceFlags ^ ourInheritanceFlags) > 0 || (fsrule.PropagationFlags ^ ourPropagationFlags) > 0)
                {
                    //This is not one of ours so leave it alone (probably the CREATOR OWNER entry)
                    continue;
                }
                else if (!dbusers.Contains(userid.Value) && (fsrule.FileSystemRights ^ (permissionsToGrant | FileSystemRights.Synchronize)) == 0)
                {
                    //This user is not in database, so remove from file system
                    directorySecurity.RemoveAccessRule(fsrule);
                    directoryInfo.SetAccessControl(directorySecurity);
                    HDCFileSystemWatcher.Message(String.Format("Removed {0} from file system (SID {1}:{2})", useracc.Value, userid.Value, grantWriteAccess ? "Write" : "Read"),TraceLevel.Warning);
                }
            }
        }
    }
}
